/*--------------------------------------------------------------------------------
 * Adaptive Intelligence - Foundation Framework Class Library
 * Version 2.00
 * 
 * A.I. Data File Base Class
 *      Provides a base class definition for implementing an A.I. file format
 * and file implementation.
 *------------------------------------------------------------------------------*/

/*--------------------------------------------------------------------------------
 * System Imports
 *------------------------------------------------------------------------------*/
using System;
using System.IO;

namespace Adaptive.Foundation.IO
{
    /// <summary>
    /// Provides a base class definition for implementing an A.I. file format
    /// and file implementation.
    /// </summary>
    public abstract class AiDataFileBase : DisposableObjectBase
    {
        /*--------------------------------------------------------------------------------
         * Public Event Definitions
         *------------------------------------------------------------------------------*/
        #region Public Event Definitions
        /// <summary>
        /// Occurs when the file is opened.
        /// </summary>
        public event EventHandler FileOpened;
        /// <summary>
        /// Occurs when the file is closed.
        /// </summary>
        public event EventHandler FileClosed;
        /// <summary>
        /// Occurs when a file data load begins.
        /// </summary>
        public event EventHandler FileLoadStart;
        /// <summary>
        /// Occurs when a file data load completes.
        /// </summary>
        public event EventHandler FileLoadComplete;
        /// <summary>
        /// Occurs during a load to indicate the status.
        /// </summary>
        public event ProgressEventHandler FileLoadProgress;
        /// <summary>
        /// Occurs when a file save begins.
        /// </summary>
        public event EventHandler FileSaveStart;
        /// <summary>
        /// Occurs when a file save completes.
        /// </summary>
        public event EventHandler FileSaveComplete;
        /// <summary>
        /// Occurs during a file save to indicate status.
        /// </summary>
        public event ProgressEventHandler FileSaveProgress;
        /// <summary>
        /// Occurs when an I/O exception occurs.
        /// </summary>
        public event ExceptionEventHandler FileIOException;
        /// <summary>
        /// Occurs when a specified file is not found.
        /// </summary>
        public event ExceptionEventHandler FileNotFound;
        /// <summary>
        /// Occurs when an A.I. data format exception occurs.
        /// </summary>
        public event ExceptionEventHandler InvalidFormatRead;
        /// <summary>
        /// Occurs just after the file header is written for any additional header data.
        /// </summary>
        public event FileStreamEventHandler PostHeaderWrite;
        /// <summary>
        /// Occurs just after the file header is read for any additional header data.
        /// </summary>
        public event FileStreamEventHandler PostHeaderRead;
        #endregion

        /*--------------------------------------------------------------------------------
         * Private Constants Declarations
         *------------------------------------------------------------------------------*/
        #region Private Constants Declarations
        /// <summary>
        /// Standard AI file format data block size.
        /// </summary>
        private const int AIBLOCKSIZE = 65536;
        #endregion

        /*--------------------------------------------------------------------------------
         * Private Member Declarations
         *------------------------------------------------------------------------------*/
        #region Private Member Declarations
        /// <summary>
        /// The path and name of the file.
        /// </summary>
        private string _fileName = string.Empty;
        /// <summary>
        /// File header.
        /// </summary>
        private AiFileHeader _header;
        /// <summary>
        /// Data content of file.
        /// </summary>
        private byte[] _content;
        /// <summary>
        /// When modified by a descendant class, indicates the number of bytes the header has been
        /// extended by.
        /// </summary>
        private int _headerAdjustment;
        #endregion

        /*--------------------------------------------------------------------------------
         * Constructor / Dispose / Destructor Methods
         *------------------------------------------------------------------------------*/
        #region Constructor / Dispose / Destructor Methods
        /// <summary>
        /// Initializes a new instance of the class.
        /// </summary>
        /// <remarks>
        /// This is the default constructor.
        /// </remarks>
        public AiDataFileBase()
            : base()
        {
            _header = new AiFileHeader(GetApplicationVersion(), GetFileFormatVersion());
            SetHeaderInfo();
        }
        /// <summary>
        /// Initializes a new instance of the class to the specified file name.
        /// </summary>
        /// <param name="fileName">
        /// The fully-qualified path and name of the file to be read or written.
        /// </param>
        public AiDataFileBase(string fileName)
            : base()
        {
            _fileName = fileName;
            _header = new AiFileHeader(GetApplicationVersion(), GetFileFormatVersion());
            SetHeaderInfo();
        }
        /// <summary>
        /// Initializes a new instance of the class to the specified file name.
        /// </summary>
        /// <param name="path">
        /// The fully-qualified path where the file is located.
        /// </param>
        /// <param name="fileName">
        /// The name of the file to be read or written.
        /// </param>
        public AiDataFileBase(string path, string fileName)
            : base()
        {
            _fileName = System.IO.Path.Combine(path, fileName);
            _header = new AiFileHeader(GetApplicationVersion(), GetFileFormatVersion());
            SetHeaderInfo();
        }
        /// <summary>
        /// Deallocates internally allocated objects.
        /// </summary>
        /// <param name="disposing">
        /// A value indicating whether the object is being disposed.
        /// </param>
        protected override void Dispose(bool disposing)
        {
            if ((!Disposed) && (disposing))
            {
                if (_header != null)
                    _header.Dispose();
            }

            _header = null;
            _fileName = null;
            base.Dispose(disposing);
        }
        #endregion

        /*--------------------------------------------------------------------------------
         * Protected Properties
         *------------------------------------------------------------------------------*/
        #region Protected Properties
        /// <summary>
        /// When modified by a descendant class, indicates the number of bytes the header has been
        /// extended by.
        /// </summary>
        /// <value>
        /// The number of bytes to add to the header size count.
        /// </value>
        protected int HeaderAdjustment
        {
            get { return _headerAdjustment; }
            set { _headerAdjustment = value; }
        }
        #endregion

        /*--------------------------------------------------------------------------------
         * Public Properties
         *------------------------------------------------------------------------------*/
        #region Public Properties
        /// <summary>
        /// Gets the reference to the file header.
        /// </summary>
        /// <value>
        /// An <see cref="AiFileHeader"/> instance containing the file header information.
        /// </value>
        public AiFileHeader Header
        {
            get { return _header; }
        }
        /// <summary>
        /// Gets or sets the name of the file.
        /// </summary>
        /// <value>
        /// A fully-qualified path and name of a file to be read or written to, or both.
        /// </value>
        public string FileName
        {
            get { return _fileName; }
            set
            {
                _fileName = value;
            }
        }
        /// <summary>
        /// Gets the current content of the file.
        /// </summary>
        /// <value>
        /// A byte array containing the data.
        /// </value>
        protected byte[] Data
        {
            get { return _content; }
            set
            {
                _content = value;
            }
        }
        #endregion

        /*--------------------------------------------------------------------------------
         * Protected Methods / Event Methods
         *------------------------------------------------------------------------------*/
        #region Protected Methods / Event Methods

        #region Event Methods
        /// <summary>
        /// Raises the <see cref="FileOpened"/> event.
        /// </summary>
        /// <param name="e">
        /// An <see cref="EventArgs"/> event arguments instance.
        /// </param>
        protected virtual void OnFileOpened(EventArgs e)
        {
            if (FileOpened != null)
                FileOpened(this, e);
        }
        /// <summary>
        /// Raises the <see cref="FileClosed"/> event.
        /// </summary>
        /// <param name="e">
        /// An <see cref="EventArgs"/> event arguments instance.
        /// </param>
        protected virtual void OnFileClosed(EventArgs e)
        {
            if (FileClosed != null)
                FileClosed(this, e);
        }
        /// <summary>
        /// Raises the <see cref="FileLoadStart"/> event.
        /// </summary>
        /// <param name="e">
        /// An <see cref="EventArgs"/> event arguments instance.
        /// </param>
        protected virtual void OnFileLoadStart(EventArgs e)
        {
            if (FileLoadStart != null)
                FileLoadStart(this, e);
        }
        /// <summary>
        /// Raises the <see cref="FileLoadComplete"/> event.
        /// </summary>
        /// <param name="e">
        /// An <see cref="EventArgs"/> event arguments instance.
        /// </param>
        protected virtual void OnFileLoadComplete(EventArgs e)
        {
            if (FileLoadComplete != null)
                FileLoadComplete(this, e);
        }
        /// <summary>
        /// Raises the <see cref="FileLoadProgress"/> event.
        /// </summary>
        /// <param name="e">
        /// An <see cref="ProgressEventArgs"/> event arguments instance 
        /// containing the current status / progress information.
        /// </param>
        protected virtual void OnFileLoadProgress(ProgressEventArgs e)
        {
            if (FileLoadProgress != null)
                FileLoadProgress(this, e);
        }
        /// <summary>
        /// Raises the <see cref="FileSaveStart"/> event.
        /// </summary>
        /// <param name="e">
        /// An <see cref="EventArgs"/> event arguments instance.
        /// </param>
        protected virtual void OnFileSaveStart(EventArgs e)
        {
            if (FileSaveStart != null)
                FileSaveStart(this, e);
        }
        /// <summary>
        /// Raises the <see cref="FileSaveComplete"/> event.
        /// </summary>
        /// <param name="e">
        /// An <see cref="EventArgs"/> event arguments instance.
        /// </param>
        protected virtual void OnFileSaveComplete(EventArgs e)
        {
            if (FileSaveComplete != null)
                FileSaveComplete(this, e);
        }
        /// <summary>
        /// Raises the <see cref="FileSaveProgress"/> event.
        /// </summary>
        /// <param name="e">
        /// An <see cref="ProgressEventArgs"/> event arguments instance 
        /// containing the current status / progress information.
        /// </param>
        protected virtual void OnFileSaveProgress(ProgressEventArgs e)
        {
            if (FileSaveProgress != null)
                FileSaveProgress(this, e);
        }
        /// <summary>
        /// Raises the <see cref="FileIOException"/> event.
        /// </summary>
        /// <param name="e">
        /// An <see cref="ExceptionEventArgs"/> event arguments instance 
        /// containing the exception data.
        /// </param>
        protected virtual void OnFileIOException(ExceptionEventArgs e)
        {
            if (FileIOException != null)
                FileIOException(this, e);
        }
        /// <summary>
        /// Raises the <see cref="FileNotFound"/> event.
        /// </summary>
        /// <param name="e">
        /// An <see cref="ExceptionEventArgs"/> event arguments instance 
        /// containing the exception data.
        /// </param>
        protected virtual void OnFileNotFound(ExceptionEventArgs e)
        {
            if (FileNotFound != null)
                FileNotFound(this, e);
        }
        /// <summary>
        /// Raises the <see cref="InvalidFormatRead"/> event.
        /// </summary>
        /// <param name="e">
        /// An <see cref="ExceptionEventArgs"/> event arguments instance 
        /// containing the exception data.
        /// </param>
        protected virtual void OnInvalidFormatRead(ExceptionEventArgs e)
        {
            if (InvalidFormatRead != null)
                InvalidFormatRead(this, e);
        }
        /// <summary>
        /// Raises the <see cref="PostHeaderWrite"/> event.
        /// </summary>
        /// <param name="e">
        /// An <see cref="FileStreamEventArgs"/> event arguments instance 
        /// containing the file stream reference.
        /// </param>
        protected virtual void OnPostHeaderWrite(FileStreamEventArgs e)
        {
            if (PostHeaderWrite != null)
                PostHeaderWrite(this, e);
        }
        /// <summary>
        /// Raises the <see cref="PostHeaderRead"/> event.
        /// </summary>
        /// <param name="e">
        /// An <see cref="FileStreamEventArgs"/> event arguments instance 
        /// containing the file stream reference.
        /// </param>
        protected virtual void OnPostHeaderRead(FileStreamEventArgs e)
        {
            if (PostHeaderRead != null)
                PostHeaderRead(this, e);
        }
        #endregion

        #region Operational Methods
        /// <summary>
        /// Sets the header information based on the format implementation.
        /// </summary>
        protected virtual void SetHeaderInfo()
        {
            _header.ApplicationName = GetApplicationName();
            _header.FileTypeDescription = GetFileTypeDescription();
            _header.FileTypeName = GetFileTypeName();
        }
        /// <summary>
        /// Processes the supplied block of data before it is written to the final content.
        /// </summary>
        /// <param name="originalData">
        /// A byte array read from a data block.
        /// </param>
        /// <returns>
        /// The processed data.
        /// </returns>
        /// <remarks>
        /// This can be implemented by classes which may perform encryption or decryption or other
        /// such operations on data after it is read from a data block.
        /// </remarks>
        protected virtual byte[] ProcessBlockAfterRead(byte[] originalData)
        {
            return originalData;
        }
        /// <summary>
        /// Processes the supplied block of data before it is written to the data block content.
        /// </summary>
        /// <param name="originalData">
        /// A byte array to write to a data block.
        /// </param>
        /// <returns>
        /// The processed data.
        /// </returns>
        /// <remarks>
        /// This can be implemented by classes which may perform encryption or decryption or other
        /// such operations on data before storing the data in a data block.
        /// </remarks>
        protected virtual byte[] ProcessBlockBeforeWrite(byte[] originalData)
        {
            return originalData;
        }
        #endregion

        #region Required Abstract Methods
        /// <summary>
        /// Gets the name of the application.
        /// </summary>
        /// <returns>
        /// The name of the application implementing the file format.
        /// </returns>
        protected abstract string GetApplicationName();
        /// <summary>
        /// Gets the description of the file format.
        /// </summary>
        /// <returns>
        /// A description of the implemented file format.
        /// </returns>
        protected abstract string GetFileTypeDescription();
        /// <summary>
        /// Gets the name of the file format.
        /// </summary>
        /// <returns>
        /// The name of the implemented file format.
        /// </returns>
        protected abstract string GetFileTypeName();
        /// <summary>
        /// Gets the implementing application version.
        /// </summary>
        /// <returns>
        /// A <see cref="Version"/> instance.
        /// </returns>
        protected abstract Version GetApplicationVersion();
        /// <summary>
        /// Gets the implemented file format version.
        /// </summary>
        /// <returns>
        /// A <see cref="Version"/> instance.
        /// </returns>
        protected abstract Version GetFileFormatVersion();
        #endregion

        #endregion

        /*--------------------------------------------------------------------------------
         * Public Methods / Functions
         *------------------------------------------------------------------------------*/
        #region Public Methods / Functions
        /// <summary>
        /// Reads the contents of a file from disk.
        /// </summary>
        /// <param name="fileName">
        /// The path and name of the file to be opened and read.
        /// </param>
        /// <returns>
        /// <b>true</b> if the operation is successful; otherwise, returns <b>false</b>.
        /// </returns>
        public bool Load(string fileName)
        {
            _fileName = fileName;
            return Load();
        }
        /// <summary>
        /// Reads the contents of a file from disk.
        /// </summary>
        /// <returns>
        /// <b>true</b> if the operation is successful; otherwise, returns <b>false</b>.
        /// </returns>
        public bool Load()
        {
            FileStream file = null;                 //File stream instance.
            bool returnValue = false;               //Return value.

            //Try to open the file.
            file = OpenFileForRead();

            if (file != null)
            {
                //Raise the event.
                OnFileOpened(EventArgs.Empty);

                //Read the header data.
                try
                {
                    _header.ReadFromStream(file);
                    OnPostHeaderRead(new FileStreamEventArgs(file));
                }
                catch (AiDataFormatException ex)
                {
                    OnInvalidFormatRead(new ExceptionEventArgs(ex));
                    file.Close();
                    file = null;
                }
                catch (IOException ex)
                {
                    OnFileIOException(new ExceptionEventArgs(ex));
                    file.Close();
                    file = null;
                }

                if (file != null)
                {
                    returnValue = ReadDataContent(file);
                    file.Close();
                    file.Dispose();
                }
                OnFileClosed(EventArgs.Empty);
            }

            return returnValue;
        }
        /// <summary>
        /// Saves the contents of a file to disk.
        /// </summary>
        /// <param name="fileName">
        /// The path and name of the file to be opened and saved.
        /// </param>
        /// <returns>
        /// <b>true</b> if the operation is successful; otherwise, returns <b>false</b>.
        /// </returns>
        public bool Save(string fileName)
        {
            _fileName = fileName;
            return Save();
        }
        /// <summary>
        /// Saves the contents of a file to disk.
        /// </summary>
        /// <returns>
        /// <b>true</b> if the operation is successful; otherwise, returns <b>false</b>.
        /// </returns>
        public bool Save()
        {
            FileStream file = null;                 //File stream instance.
            bool returnValue = false;               //Return value.

            //Try to open the file.
            file = OpenFileForWrite();

            if (file != null)
            {
                //Raise the event.
                OnFileOpened(EventArgs.Empty);

                //Read the header data.
                try
                {
                    _header.WriteToStream(file);
                    file.Flush();
                    OnPostHeaderWrite(new FileStreamEventArgs(file));
                }
                catch (IOException ex)
                {
                    OnFileIOException(new ExceptionEventArgs(ex));
                    file.Close();
                    file = null;
                }

                if (file != null)
                {
                    returnValue = SaveDataContent(file);
                    file.Close();
                    file.Dispose();
                }
                OnFileClosed(EventArgs.Empty);
            }

            return returnValue;
        }
        #endregion

        /*--------------------------------------------------------------------------------
         * Private Methods / Functions
         *------------------------------------------------------------------------------*/
        #region Private Methods / Functions
        /// <summary>
        /// Attempts to open a file for reading.
        /// </summary>
        /// <returns>
        /// A <see cref="FileStream"/> instance, if successful.
        /// </returns>
        private FileStream OpenFileForRead()
        {
            FileStream returnValue = null;              //Return value.

            if (!File.Exists(_fileName))
            {
                OnFileNotFound(new ExceptionEventArgs(new FileNotFoundException()));
            }
            else
            {
                try
                {
                    returnValue = new FileStream(_fileName, FileMode.Open, FileAccess.Read);
                }
                catch (IOException ex)
                {
                    OnFileIOException(new ExceptionEventArgs(ex));
                    returnValue = null;
                }
            }
            return returnValue;
        }
        /// <summary>
        /// Reads the post-header content of the data file.
        /// </summary>
        /// <param name="fileToRead">
        /// An open <see cref="FileStream"/> containing the data to be read.
        /// </param>
        /// <returns>
        /// <b>true</b> if the data was read successfully; otherwise, <b>false</b>.
        /// </returns>
        private bool ReadDataContent(FileStream fileToRead)
        {
            BinaryReader reader = null;                         //Data reader.
            ValidatedDataContainerCollection dataList = null;   //Data blocks to be read and verified.
            bool returnValue = false;                           //Return value.

            //File load start event.
            OnFileLoadStart(EventArgs.Empty);

            //Create objects.
            reader = new BinaryReader(fileToRead);
            dataList = new ValidatedDataContainerCollection();
            dataList.DataBlockSize = AIBLOCKSIZE;

            //Move the current pointer to the data stream after the file header.
            fileToRead.Seek(_header.EndOfHeaderPosition + _headerAdjustment, SeekOrigin.Begin);

            try
            {
                dataList.LoadFromStream(reader);
                returnValue = true;
                CopyData(dataList);

                //Raise event.
                OnFileLoadComplete(EventArgs.Empty);
            }
            catch (AiCrcFailedException crcEx)
            {
                OnFileIOException(new ExceptionEventArgs(crcEx));
            }
            catch (AiHashCodeFailedException hashEx)
            {
                OnFileIOException(new ExceptionEventArgs(hashEx));
            }
            catch (AiDataFormatException formatEx)
            {
                OnInvalidFormatRead(new ExceptionEventArgs(formatEx));
            }
            catch (IOException ioEx)
            {
                OnFileIOException(new ExceptionEventArgs(ioEx));
            }

            //Close and return
            dataList.Dispose();
            reader.Close();

            return returnValue;

        }
        /// <summary>
        /// Copies the data from the data blocks list to a byte array.
        /// </summary>
        /// <param name="dataList">
        /// The list of data blocks to be copied.
        /// </param>
        private void CopyData(ValidatedDataContainerCollection dataList)
        {
            MemoryStream stream = null;
            BinaryWriter writer = null;
            ProgressEventArgs evArgs = null;
            int count = 0;
            int length = 0;

            //Raise initial event.
            evArgs = new ProgressEventArgs();
            OnFileLoadProgress(evArgs);

            //Create copy destination.
            stream = new MemoryStream();
            writer = new BinaryWriter(stream);

            //Copy data contents.
            length = dataList.Count;
            for (count = 0; count < length; count++)
            {
                writer.Write(ProcessBlockAfterRead(dataList[count].Data));
                evArgs.PercentComplete = (int)(((float)count / length) * 100);
                OnFileLoadProgress(evArgs);
            }

            //Raise final event.
            evArgs.PercentComplete = 100;
            OnFileLoadProgress(evArgs);

            //Store the data, clear, and return.
            writer.Flush();
            _content = stream.ToArray();
            writer.Close();
            stream.Close();
            stream.Dispose();
        }
        /// <summary>
        /// Attempts to open a file for writing.
        /// </summary>
        /// <returns>
        /// A <see cref="FileStream"/> instance, if successful.
        /// </returns>
        private FileStream OpenFileForWrite()
        {
            FileStream returnValue = null;              //Return value.

            if (File.Exists(_fileName))
                File.Delete(_fileName);
            try
            {
                returnValue = new FileStream(_fileName, FileMode.CreateNew, FileAccess.Write);
            }
            catch (IOException ex)
            {
                OnFileIOException(new ExceptionEventArgs(ex));
                returnValue = null;
            }
            return returnValue;
        }
        /// <summary>
        /// Copies the data from content array into a validated data container list.
        /// </summary>
        /// <returns>
        /// A <see cref="ValidatedDataContainerCollection"/> instance containing the data
        /// blocks to be written to disk.
        /// </returns>
        private ValidatedDataContainerCollection StoreData()
        {
            ValidatedDataContainerCollection returnValue = null;
            MemoryStream container = null;

            //Parse the content into data blocks.
            returnValue = new ValidatedDataContainerCollection();
            container = new MemoryStream(_content);
            returnValue.CreateFromStream(container);
            container.Close();

            //Process each block.
            returnValue.MoveFirst();
            while (!returnValue.Eof)
            {
                returnValue.Value.Data = ProcessBlockBeforeWrite(returnValue.Value.Data);
                returnValue.MoveNext();
            }

            return returnValue;
        }
        /// <summary>
        /// Writes the post-header content of the data file.
        /// </summary>
        /// <param name="fileToWrite">
        /// An open <see cref="FileStream"/> containing the stream to be written to.
        /// </param>
        /// <returns>
        /// <b>true</b> if the data was written successfully; otherwise, <b>false</b>.
        /// </returns>
        private bool SaveDataContent(FileStream fileToWrite)
        {
            BinaryWriter writer = null;                         //Data writer.
            ValidatedDataContainerCollection dataList = null;   //Data blocks to be read and verified.
            bool returnValue = false;                           //Return value.
            ProgressEventArgs evArgs = null;                    //Progress event arguments.
            int count = 0;
            int length = 0;

            //File load start event.
            OnFileSaveStart(EventArgs.Empty);

            //Create objects and parse the data to be stored.
            writer = new BinaryWriter(fileToWrite);
            dataList =  StoreData();
            evArgs = new ProgressEventArgs();

           length = dataList.Count;
           do
           {
               //Raise update event.
               evArgs.PercentComplete = (int)(((float)count / length) * 100);
               OnFileSaveProgress(evArgs);

               //Save each block.
               try
               {
                   dataList[count].WriteToStream(writer);
                   returnValue = true;
               }
               catch (AiCrcFailedException crcEx)
               {
                   OnFileIOException(new ExceptionEventArgs(crcEx));
                   returnValue = false;
               }
               catch (AiHashCodeFailedException hashEx)
               {
                   OnFileIOException(new ExceptionEventArgs(hashEx));
                   returnValue = false;
               }
               catch (AiDataFormatException formatEx)
               {
                   OnInvalidFormatRead(new ExceptionEventArgs(formatEx));
                   returnValue = false;
               }
               catch (IOException ioEx)
               {
                   OnFileIOException(new ExceptionEventArgs(ioEx));
                   returnValue = false;
               }
               count++;
           } while ((count < length) && (returnValue));

           if (returnValue)
               OnFileSaveComplete(EventArgs.Empty);

            //Close and return
            dataList.Dispose();
            writer.Close();

            return returnValue;
        }
        #endregion
    }
}